package cn.schoolwow.quickserver.handler;

import cn.schoolwow.quickserver.controller.RequestMethod;
import cn.schoolwow.quickserver.controller.annotation.*;
import cn.schoolwow.quickserver.domain.Client;
import cn.schoolwow.quickserver.domain.ControllerMeta;
import cn.schoolwow.quickserver.domain.MultipartFile;
import cn.schoolwow.quickserver.request.HttpRequest;
import cn.schoolwow.quickserver.response.HttpResponse;
import cn.schoolwow.quickserver.response.HttpStatus;
import cn.schoolwow.quickserver.session.HttpSession;
import cn.schoolwow.quickserver.util.AntPatternMatcher;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.util.TypeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.HttpCookie;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 控制器处理
 */
public class ControllerHandler implements Handler {
    private Logger logger = LoggerFactory.getLogger(ControllerHandler.class);

    @Override
    public Handler handle(Client client) throws Exception {
        ControllerMeta controllerMeta = getControllerMeta(client);
        if(null==controllerMeta){
            return new StaticResourceHandler();
        }
        //处理BasicAuth认证
        if(!handleBasicAuth(client,controllerMeta)){
            return new HttpResponseHandler();
        }
        //处理跨域请求
        handleCrossOrigin(client, controllerMeta);
        //调用控制器方法
        return invokeControllerMethod(client, controllerMeta);
    }

    /**
     * 查找Controller
     */
    private ControllerMeta getControllerMeta(Client client) {
        for (ControllerMeta controllerMeta : client.serverConfigMeta.controllerMetaList) {
            int urlPos = 0, mappingPos = 0, lastUrlPos = 0, lastMappingPos = 0;
            String requestPath = client.httpRequestMeta.uri.getPath();
            String mappingUrl = controllerMeta.mappingUrl;
            String antRequestUrl = mappingUrl;
            while (urlPos < requestPath.length() && mappingPos < mappingUrl.length()) {
                if (mappingUrl.charAt(mappingPos) == '{') {
                    lastUrlPos = urlPos;
                    lastMappingPos = mappingPos + 1;

                    while (mappingPos < mappingUrl.length() && mappingUrl.charAt(mappingPos) != '}') {
                        mappingPos++;
                    }
                    if (mappingPos < mappingUrl.length()) {
                        //提取变量名
                        String name = mappingUrl.substring(lastMappingPos, mappingPos);
                        antRequestUrl = antRequestUrl.replace("{" + name + "}", "*");
                        String value = null;
                        //提取变量值
                        if (mappingPos + 1 < mappingUrl.length()) {
                            while (urlPos < requestPath.length() && requestPath.charAt(urlPos) != mappingUrl.charAt(mappingPos + 1)) {
                                urlPos++;
                            }
                            if (urlPos < requestPath.length()) {
                                value = requestPath.substring(lastUrlPos, urlPos);
                            }
                        } else {
                            value = requestPath.substring(lastUrlPos);
                        }
                        client.httpRequestMeta.pathVariable.put(name, value);
                    }
                } else if (requestPath.charAt(urlPos) == mappingUrl.charAt(mappingPos)) {
                    urlPos++;
                    mappingPos++;
                } else {
                    mappingPos++;
                }
            }
            if ((mappingUrl.contains("{") && AntPatternMatcher.match(requestPath, antRequestUrl))
                    || requestPath.equals(mappingUrl)) {
                //判断请求方法是否匹配
                for (RequestMethod requestMethod : controllerMeta.requestMethodList) {
                    if (requestMethod.name().equalsIgnoreCase(client.httpRequestMeta.method)) {
                        logger.trace("[匹配Controller]路径:{},处理方法:{}", client.httpRequestMeta.uri.getPath(), controllerMeta.method.toString());
                        return controllerMeta;
                    }
                }
            }
        }
        logger.trace("[未匹配Controller]路径:{}", client.httpRequestMeta.uri.getPath());
        return null;
    }

    /**
     * 处理BasicAuth注解
     * @return 是否继续往下执行
     */
    public boolean handleBasicAuth(Client client, ControllerMeta controllerMeta) {
        BasicAuth basicAuth = controllerMeta.method.getAnnotation(BasicAuth.class);
        if (null == basicAuth) {
            basicAuth = controllerMeta.method.getDeclaringClass().getAnnotation(BasicAuth.class);
        }
        if (null == basicAuth) {
            return true;
        }
        logger.debug("[BasicAuth]地址:{},用户名:{},密码:{}", client.httpRequestMeta.uri, basicAuth.username(), basicAuth.password());
        if (client.httpRequestMeta.headers.containsKey("Authorization")) {
            String authorization = client.httpRequestMeta.headers.get("Authorization").get(0);
            authorization = authorization.substring(authorization.indexOf("Basic ") + 6);
            String expectAuthorization = new String(Base64.getEncoder().encode((basicAuth.username() + ":" + basicAuth.password()).getBytes()));
            if(authorization.equals(expectAuthorization)){
                return true;
            }
        }
        client.httpResponseMeta.httpStatus(HttpStatus.UNAUTHORIZED);
        client.httpResponseMeta.headers.put("WWW-Authenticate", Arrays.asList("Basic realm=\"" + basicAuth.realm() + "\""));
        return false;
    }

    /**
     * 处理跨域请求注解
     */
    private void handleCrossOrigin(Client client, ControllerMeta controllerMeta) {
        CrossOrigin crossOriginAnnotation = controllerMeta.method.getDeclaredAnnotation(CrossOrigin.class);
        if (null == crossOriginAnnotation) {
            crossOriginAnnotation = controllerMeta.method.getDeclaringClass().getDeclaredAnnotation(CrossOrigin.class);
        }
        if (null == crossOriginAnnotation) {
            return;
        }
        logger.debug("[CrossOrigin]处理跨域请求:{}",controllerMeta.method.getName());
        if (!client.httpRequestMeta.headers.containsKey("Origin")) {
            client.httpResponseMeta.httpStatus(HttpStatus.BAD_REQUEST);
            logger.warn("[CrossOrigin]服务端为跨域请求,但客户端无Origin头部!");
            return;
        }
        String origin = client.httpRequestMeta.headers.get("Origin").get(0);
        //检查origin
        boolean allowOrigin = false;
        if (crossOriginAnnotation.origins().length == 0) {
            allowOrigin = true;
        } else {
            String[] crossOrigins = crossOriginAnnotation.origins();
            for (String crossOrigin : crossOrigins) {
                if (origin.equalsIgnoreCase(crossOrigin) || "*".equalsIgnoreCase(crossOrigin)) {
                    allowOrigin = true;
                    break;
                }
            }
        }
        if (!allowOrigin) {
            logger.warn("[CrossOrigin]origin头部不匹配,跨域失败!客户端Origin头部:{}",origin);
            return;
        }
        //检查请求方法
        String accessControlRequestMethod = null;
        if (client.httpRequestMeta.headers.containsKey("Access-Control-Allow-Methods")) {
            accessControlRequestMethod = client.httpRequestMeta.headers.get("Access-Control-Allow-Methods").get(0);
        }
        if (null != accessControlRequestMethod && crossOriginAnnotation.methods().length > 0) {
            boolean allowMethod = false;
            for (String method : crossOriginAnnotation.methods()) {
                if (accessControlRequestMethod.equalsIgnoreCase(method)) {
                    allowMethod = true;
                    break;
                }
            }
            if (!allowMethod) {
                logger.warn("[CrossOrigin]请求方法不允许!客户端头部Access-Control-Allow-Methods:{}",accessControlRequestMethod);
                return;
            }
        }
        //检查请求头部
        String accessControlRequestHeaders = null;
        if (client.httpRequestMeta.headers.containsKey("Access-Control-Allow-Headers")) {
            accessControlRequestHeaders = client.httpRequestMeta.headers.get("Access-Control-Allow-Headers").get(0);
        }
        if (null != accessControlRequestHeaders && crossOriginAnnotation.headers().length > 0) {
            String[] requestHeaders = accessControlRequestHeaders.split(",");
            for (String allowHeader : crossOriginAnnotation.headers()) {
                boolean exist = false;
                for (String requestHeader : requestHeaders) {
                    if ("*".equals(allowHeader) || requestHeader.equals(allowHeader)) {
                        exist = true;
                        break;
                    }
                }
                if (!exist) {
                    logger.warn("[CrossOrigin]请求头部不允许!客户端头部Access-Control-Allow-Headers:{}",accessControlRequestHeaders);
                    return;
                }
            }
        }
        //缓存跨域响应头部信息
        if (!client.serverConfigMeta.crossOriginMap.containsKey(client.httpRequestMeta.uri)) {
            //设置跨域头部信息
            Map<String, List<String>> crossOriginMap = new HashMap<>();
            crossOriginMap.put("Access-Control-Allow-Origin", Arrays.asList(crossOriginAnnotation.origins().length == 0 ? "*" : origin));
            crossOriginMap.put("Access-Control-Max-Age", Arrays.asList(crossOriginAnnotation.maxAge() + ""));
            if (crossOriginAnnotation.allowCredentials()) {
                crossOriginMap.put("Access-Control-Allow-Credentials", Arrays.asList("true"));
            }
            if (crossOriginAnnotation.methods().length > 0) {
                StringBuffer stringBuffer = new StringBuffer();
                for (String method : crossOriginAnnotation.methods()) {
                    stringBuffer.append(method + ",");
                }
                stringBuffer.deleteCharAt(stringBuffer.length() - 1);
                crossOriginMap.put("Access-Control-Allow-Methods", Arrays.asList(stringBuffer.toString()));
            } else if (null != accessControlRequestMethod) {
                crossOriginMap.put("Access-Control-Allow-Methods", Arrays.asList(accessControlRequestMethod));
            }
            StringBuilder builder = new StringBuilder();
            if (crossOriginAnnotation.headers().length > 0) {
                for (String header : crossOriginAnnotation.headers()) {
                    builder.append(header + ",");
                }
                builder.deleteCharAt(builder.length() - 1);
                crossOriginMap.put("Access-Control-Allow-Headers", Arrays.asList(builder.toString()));
            } else if (null != accessControlRequestHeaders) {
                crossOriginMap.put("Access-Control-Allow-Headers", Arrays.asList(accessControlRequestHeaders));
            }
            if (crossOriginAnnotation.exposedHeaders().length > 0) {
                builder.setLength(0);
                for (String exposedHeader : crossOriginAnnotation.exposedHeaders()) {
                    builder.append(exposedHeader + ",");
                }
                builder.deleteCharAt(builder.length() - 1);
                crossOriginMap.put("Access-Control-Expose-Headers", Arrays.asList(builder.toString()));
            }
            client.serverConfigMeta.crossOriginMap.put(client.httpRequestMeta.uri, crossOriginMap);
        }
        client.httpResponseMeta.headers.putAll(client.serverConfigMeta.crossOriginMap.get(client.httpRequestMeta.uri));
    }

    /**
     * 获取方法调用参数
     */
    public Handler invokeControllerMethod(Client client, ControllerMeta controllerMeta) throws Exception {
        Method method = controllerMeta.method;
        Object[] parameterArray = new Object[method.getParameterCount()];
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Object parameterValue = getAutoWireType(client, parameter);
            if (null == parameterValue) {
                parameterValue = getPathVariable(client, parameter);
            }
            if (null == parameterValue) {
                parameterValue = getRequestParam(client, parameter);
            }
            if (null == parameterValue) {
                parameterValue = getRequestPart(client, parameter);
            }
            if (null == parameterValue) {
                parameterValue = getRequestBody(client, parameter);
            }
            if (null == parameterValue) {
                parameterValue = getSessionValue(client, parameter);
            }
            if (null == parameterValue) {
                parameterValue = getRequestHeader(client, parameter);
            }
            if (null == parameterValue) {
                parameterValue = getCookieValue(client, parameter);
            }
            if (null == parameterValue) {
                parameterValue = getCompositeParameter(client, parameter);
            }

            if (null != parameterValue) {
                //参数类型转换
                parameterArray[i] = TypeUtils.cast(parameterValue, parameter.getType(), null);
            }
        }
        Object result = method.invoke(controllerMeta.instance, parameterArray);
        if (null != client.serverConfigMeta.responseBodyAdvice && client.serverConfigMeta.responseBodyAdvice.support(method)) {
            result = client.serverConfigMeta.responseBodyAdvice.beforeBodyWrite(result, method, client.httpRequest, client.httpResponse, client.httpSession);
        }
        return handleResult(client, result);
    }

    /**
     * 获取自动装配类型
     */
    private Object getAutoWireType(Client client, Parameter parameter) {
        if (parameter.getType().getName().equals(HttpRequest.class.getName())) {
            return client.httpRequest;
        } else if (parameter.getType().getName().equals(HttpResponse.class.getName())) {
            return client.httpResponse;
        } else if (parameter.getType().getName().equals(HttpSession.class.getName())) {
            return client.httpSession;
        }
        return null;
    }

    /**
     * 处理PathVariable注解
     */
    private Object getPathVariable(Client client, Parameter parameter) {
        PathVariable pathVariable = parameter.getAnnotation(PathVariable.class);
        if (null == pathVariable) {
            return null;
        }
        if (pathVariable.required() && !client.httpRequestMeta.pathVariable.containsKey(pathVariable.name())) {
            throw new IllegalArgumentException("路径变量[" + pathVariable.name() + "]不能为空!");
        }
        return client.httpRequestMeta.pathVariable.get(pathVariable.name());
    }

    /**
     * 处理RequestParam注解
     */
    private Object getRequestParam(Client client, Parameter parameter) throws ParseException {
        RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
        if (null == requestParam) {
            return null;
        }
        String requestParameter = client.httpRequestMeta.dataMap.get(requestParam.name());
        if (requestParam.required() && requestParameter == null) {
            throw new IllegalArgumentException("表单请求参数[" + requestParam.name() + "]不能为空!");
        }
        if (requestParameter == null) {
            requestParameter = requestParam.defaultValue();
        }
        //处理日期类型
        String pattern = requestParam.pattern();
        if (requestParam.required() && !pattern.isEmpty() && requestParameter.isEmpty()) {
            throw new IllegalArgumentException("表单请求参数[" + requestParam.name() + "]不能为空!");
        }
        if (!pattern.isEmpty() && !requestParameter.isEmpty()) {
            return getDateParameterByType(pattern, parameter.getType(), requestParameter);
        }
        return requestParameter;
    }

    /**
     * 处理RequestPart注解
     */
    private Object getRequestPart(Client client, Parameter parameter) throws ParseException {
        RequestPart requestPart = parameter.getAnnotation(RequestPart.class);
        if (null == requestPart) {
            return null;
        }
        switch (parameter.getType().getName()) {
            case "cn.schoolwow.quickserver.domain.MultipartFile": {
                List<MultipartFile> multipartFileList = client.httpRequestMeta.fileParameterMap.get(requestPart.name());
                if (requestPart.required() && (multipartFileList == null || multipartFileList.isEmpty())) {
                    throw new IllegalArgumentException("表单请求参数[" + requestPart.name() + "]不能为空!");
                }
                if (null == multipartFileList || multipartFileList.isEmpty()) {
                    return null;
                }
                return multipartFileList.get(0);
            }
            case "[Lcn.schoolwow.quickserver.domain.MultipartFile;": {
                List<MultipartFile> multipartFileList = client.httpRequestMeta.fileParameterMap.get(requestPart.name());
                if (requestPart.required() && (multipartFileList == null || multipartFileList.isEmpty())) {
                    throw new IllegalArgumentException("表单请求参数[" + requestPart.name() + "]不能为空!");
                }
                if (null == multipartFileList || multipartFileList.isEmpty()) {
                    return null;
                }
                return multipartFileList;
            }
            default: {
                if (requestPart.required() && !client.httpRequestMeta.dataMap.containsKey(requestPart.name())) {
                    throw new IllegalArgumentException("表单请求参数[" + requestPart.name() + "]不能为空!");
                }
                String requestParameter = client.httpRequestMeta.dataMap.get(requestPart.name());
                if (requestParameter == null) {
                    requestParameter = requestPart.defaultValue();
                }
                //处理日期类型
                String pattern = requestPart.pattern();
                if (requestPart.required() && !pattern.isEmpty() && requestParameter.isEmpty()) {
                    throw new IllegalArgumentException("表单请求参数[" + requestPart.name() + "]不能为空!");
                }
                if (!pattern.isEmpty() && !requestParameter.isEmpty()) {
                    return getDateParameterByType(pattern, parameter.getType(), requestParameter);
                }
                return requestParameter;
            }
        }
    }

    /**
     * 处理RequestBody注解
     */
    private Object getRequestBody(Client client, Parameter parameter) {
        RequestBody requestBody = parameter.getAnnotation(RequestBody.class);
        if (null == requestBody) {
            return null;
        }
        Class type = parameter.getType();
        if (isPrimitiveType(type)) {
            return TypeUtils.cast(client.httpRequestMeta.body, parameter.getType(), null);
        } else if (type.getName().equals(String.class.getName())) {
            return client.httpRequestMeta.body;
        } else if (client.httpRequestMeta.body.startsWith("{")) {
            return JSON.parseObject(client.httpRequestMeta.body).toJavaObject(parameter.getType());
        } else if (client.httpRequestMeta.body.startsWith("[")) {
            return JSON.parseArray(client.httpRequestMeta.body).toJavaList(parameter.getType().getComponentType());
        } else if (requestBody.required()) {
            throw new IllegalArgumentException("@RequestBody注解无法解析该JSON参数!参数名称:" + parameter.getName() + ",参数值:" + client.httpRequestMeta.body);
        }
        return null;
    }

    /**
     * 处理SessionValue注解
     */
    private Object getSessionValue(Client client, Parameter parameter) {
        SessionValue sessionValue = parameter.getAnnotation(SessionValue.class);
        if (null == sessionValue) {
            return null;
        }
        if (sessionValue.required() && !client.httpSessionMeta.attributes.containsKey(sessionValue.name())) {
            throw new IllegalArgumentException("SessionValue注解[" + sessionValue.name() + "]不能为空!");
        }
        Object parameterValue = client.httpSessionMeta.attributes.get(sessionValue.name());
        logger.trace("[SessionValue]name:{},value:{},defaultValue:{}", sessionValue.name(), parameterValue, sessionValue.defaultValue());
        if (null == parameterValue) {
            parameterValue = sessionValue.defaultValue();
        }
        return parameterValue;
    }

    /**
     * 处理RequestHeader注解
     */
    private Object getRequestHeader(Client client, Parameter parameter) {
        RequestHeader requestHeader = parameter.getAnnotation(RequestHeader.class);
        if (null == requestHeader) {
            return null;
        }
        List<String> requestHeaderValues = client.httpRequestMeta.headers.get(requestHeader.name());
        if (requestHeader.required() && null == requestHeaderValues) {
            throw new IllegalArgumentException("http头部[" + requestHeader.name() + "]不能为空!");
        }
        Object parameterValue = null == requestHeaderValues ? null : requestHeaderValues.get(0);
        logger.trace("[RequestHeader]name:{},value:{},defaultValue:{}", requestHeader.name(), parameterValue, requestHeader.defaultValue());
        if (null == parameterValue) {
            parameterValue = requestHeader.defaultValue();
        }
        return parameterValue;
    }

    /**
     * 处理CookieValue注解
     */
    private Object getCookieValue(Client client, Parameter parameter) {
        CookieValue cookieValue = parameter.getAnnotation(CookieValue.class);
        if (null == cookieValue) {
            return null;
        }
        HttpCookie httpCookie = client.httpRequestMeta.getHttpCookieByName(cookieValue.name());
        if (null == httpCookie) {
            throw new IllegalArgumentException("Cookie[" + cookieValue.name() + "]不存在!");
        }
        return httpCookie.getValue();
    }

    private Object getCompositeParameter(Client client, Parameter parameter) throws Exception {
        Class parameterType = parameter.getType();
        if (isPrimitiveType(parameterType) || "java.lang.String".equals(parameterType.getName())) {
            return null;
        }
        Object instance = parameterType.newInstance();
        Field[] fields = parameterType.getDeclaredFields();
        Field.setAccessible(fields, true);
        for (Field field : fields) {
            if (client.httpRequestMeta.dataMap.containsKey(field.getName())) {
                field.set(instance, client.httpRequestMeta.dataMap.get(field.getName()));
            }
        }
        return instance;
    }

    /**
     * 处理返回结果
     */
    private Handler handleResult(Client client, Object result) throws IOException {
        if(null!=result){
            String resultString = result.toString();
            if (resultString.startsWith("redirect:")) {
                client.httpResponseMeta.httpStatus(HttpStatus.FOUND);
                client.httpResponseMeta.headers.put("Location", Arrays.asList(resultString.substring("redirect:".length())));
            } else if (isPrimitiveType(result.getClass()) || result instanceof String) {
                client.httpResponseMeta.contentType = "text/plain";
                client.httpResponseMeta.result = String.valueOf(result);
                byte[] bytes = client.httpResponseMeta.result.getBytes(client.httpResponseMeta.charset);
                client.httpResponseMeta.contentLength = bytes.length;
                client.httpResponseMeta.bodyInputStream = new ByteArrayInputStream(bytes);
            } else {
                client.httpResponseMeta.contentType = "application/json";
                client.httpResponseMeta.result = JSON.toJSONString(result);
                byte[] bytes = client.httpResponseMeta.result.getBytes(client.httpResponseMeta.charset);
                client.httpResponseMeta.contentLength = bytes.length;
                client.httpResponseMeta.bodyInputStream = new ByteArrayInputStream(bytes);
            }
        }
        return new HttpResponseHandler();
    }

    /**
     * 根据pattern和参数类型转换日期类型参数
     *
     * @param pattern          日期格式
     * @param parameterType    参数类型
     * @param requestParameter 请求参数
     */
    private Object getDateParameterByType(String pattern, Class parameterType, String requestParameter) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        Date date = sdf.parse(requestParameter);
        switch (parameterType.getName()) {
            case "java.util.Date": {
                return date;
            }
            case "java.sql.Date": {
                return new java.sql.Date(date.getTime());
            }
            case "java.sql.Timestamp": {
                return new Timestamp(date.getTime());
            }
            case "java.util.Calendar": {
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(date);
                return calendar;
            }
            case "java.time.LocalDate": {
                return LocalDate.parse(requestParameter, DateTimeFormatter.ofPattern(pattern));
            }
            case "java.time.LocalDateTime": {
                return LocalDateTime.parse(requestParameter, DateTimeFormatter.ofPattern(pattern));
            }
            default: {
                throw new IllegalArgumentException("不支持该日期类型,目前支持的类型为Date,Calendar,LocalDate,LocalDateTime!当前类型:" + parameterType.getName());
            }
        }
    }

    /**
     * 是否为基本数据类型
     *
     * @param clazz 类型
     */
    private boolean isPrimitiveType(Class<?> clazz) {
        try {
            if (clazz.isPrimitive()) {
                return true;
            }
            return ((Class<?>) clazz.getField("TYPE").get(null)).isPrimitive();
        } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
            return false;
        }
    }
}
